--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 5ceb0c671e4a2020efaa627d3f118af76ebcc5c4
Parents : 3e7e55a
Author : Mark Qvist <mark@unsigned.io>
Date : 2025-12-27T13:29:10+01:00
Fixed composite unicode characters breaking text alignment rendering
Changes
5 files changed, 42 insertions(+), 9 deletions(-)
Diff
diff --git a/nomadnet/Directory.py b/nomadnet/Directory.py
index 0286084..5e33576 100644
--- a/nomadnet/Directory.py
+++ b/nomadnet/Directory.py
@@ -7,6 +7,7 @@ import threading
import RNS.vendor.umsgpack as msgpack
from LXMF import pn_announce_data_is_valid
+from nomadnet.util import strip_modifiers
class PNAnnounceHandler:
def __init__(self, owner):
@@ -229,7 +230,7 @@ class Directory:
def display_name(self, source_hash):
if source_hash in self.directory_entries:
- return self.directory_entries[source_hash].display_name
+ return strip_modifiers(self.directory_entries[source_hash].display_name)
else:
return None
@@ -241,7 +242,7 @@ class Directory:
if dn == None:
return RNS.prettyhexrep(source_hash)
else:
- return dn+" <"+RNS.hexrep(source_hash, delimit=False)+">"
+ return strip_modifiers(dn)+" <"+RNS.hexrep(source_hash, delimit=False)+">"
else:
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
else:
@@ -250,13 +251,13 @@ class Directory:
if dn == None:
return RNS.prettyhexrep(source_hash)
else:
- return dn
+ return strip_modifiers(dn)
else:
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
def alleged_display_str(self, source_hash):
if source_hash in self.directory_entries:
- return self.directory_entries[source_hash].display_name
+ return strip_modifiers(self.directory_entries[source_hash].display_name)
else:
return None
diff --git a/nomadnet/ui/textui/Browser.py b/nomadnet/ui/textui/Browser.py
index 4ff35d8..ef37b61 100644
--- a/nomadnet/ui/textui/Browser.py
+++ b/nomadnet/ui/textui/Browser.py
@@ -11,6 +11,7 @@ import threading
from .MicronParser import markup_to_attrmaps, make_style, default_state
from nomadnet.Directory import DirectoryEntry
from nomadnet.vendor.Scrollable import *
+from nomadnet.util import strip_modifiers
class BrowserFrame(urwid.Frame):
def keypress(self, size, key):
@@ -813,7 +814,7 @@ class Browser:
fg = self.markup[fgpos+5:endpos]
self.page_foreground_color = fg
- self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
+ self.attr_maps = markup_to_attrmaps(strip_modifiers(self.markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
self.response_progress = 0
self.response_speed = None
@@ -880,7 +881,7 @@ class Browser:
fg = self.markup[fgpos+5:endpos]
self.page_foreground_color = fg
- self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
+ self.attr_maps = markup_to_attrmaps(strip_modifiers(self.markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
self.response_progress = 0
self.response_speed = None
@@ -1032,7 +1033,7 @@ class Browser:
fg = self.markup[fgpos+5:endpos]
self.page_foreground_color = fg
- self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
+ self.attr_maps = markup_to_attrmaps(strip_modifiers(self.markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
self.response_progress = 0
self.response_speed = None
self.progress_updated_at = None
diff --git a/nomadnet/ui/textui/Helpers.py b/nomadnet/ui/textui/Helpers.py
new file mode 100644
index 0000000..e69de29
diff --git a/nomadnet/ui/textui/Network.py b/nomadnet/ui/textui/Network.py
index 3edf31b..242118d 100644
--- a/nomadnet/ui/textui/Network.py
+++ b/nomadnet/ui/textui/Network.py
@@ -6,6 +6,7 @@ import threading
from datetime import datetime
from nomadnet.Directory import DirectoryEntry
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox, MODIFIER_KEY
+from nomadnet.util import strip_modifiers
from .Browser import Browser
@@ -279,7 +280,7 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
display_str = RNS.hexrep(source_hash, delimit=False)
else:
try:
- display_str = announce[2].decode("utf-8")
+ display_str = strip_modifiers(announce[2].decode("utf-8"))
if len(display_str) > 32:
display_str = display_str[:32] + "..."
except:
@@ -608,7 +609,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
if node_entry == None:
display_str = self.app.directory.simplest_display_str(source_hash)
else:
- display_str = node_entry.display_name
+ display_str = strip_modifiers(node_entry.display_name)
addr_str = "<"+RNS.hexrep(source_hash, delimit=False)+">"
diff --git a/nomadnet/util.py b/nomadnet/util.py
new file mode 100644
index 0000000..d1d38c4
--- /dev/null
+++ b/nomadnet/util.py
@@ -0,0 +1,30 @@
+import re
+import unicodedata
+import RNS
+
+def strip_modifiers(text):
+ def process_characters(text):
+ result = []
+ i = 0
+ while i < len(text):
+ char = text[i]
+ category = unicodedata.category(char)
+
+ if category.startswith(('L', 'N', 'P', 'S')):
+ result.append(char)
+ i += 1
+ elif category.startswith(('M', 'Sk', 'Cf')) or char in '\u200d\u200c':
+ i += 1
+ else:
+ result.append(char)
+ i += 1
+
+ return ''.join(result)
+
+ stripped = process_characters(text)
+ stripped = re.sub(r'[\uFE00-\uFE0F]', '', stripped)
+ stripped = re.sub(r'[\U000E0100-\U000E01EF]', '', stripped, flags=re.UNICODE)
+ stripped = re.sub(r'[\U0001F3FB-\U0001F3FF]', '', stripped, flags=re.UNICODE)
+ stripped = re.sub(r'[\u200D\u200C]', '', stripped)
+
+ return stripped
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────